// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。去工作用英语怎么说。dev/ref/mod#workspaces 


package workcmd

import (
	"cmd/go/internal/base"
	"cmd/go/internal/fsys"
	"cmd/go/internal/modload"
	"cmd/go/internal/str"
	"context"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
)

var cmdUse = &base.Command{
	UsageLine: "go work use [-r] moddirs",
	Short:     "add modules to workspace file",
	Long: `Use provides a command-line interface for adding
directories, optionally recursively, to a go.work file.

A use directive will be added to the go.work file for each argument
directory listed on the command line go.work file, if it exists on disk,
or removed from the go.work file if it does not exist on disk.

The -r flag searches recursively for modules in the argument
directories, and the use command operates as if each of the directories
were specified as arguments: namely, use directives will be added for
directories that exist, and removed for directories that do not exist.

for more information.
`,
}

var useR = cmdUse.Flag.Bool("r", false, "")

func init() {
	cmdUse.Run = runUse // break init cycle 

	base.AddModCommonFlags(&cmdUse.Flag)
}

func runUse(ctx context.Context, cmd *base.Command, args []string) {
	modload.ForceUseModules = true

	var gowork string
	modload.InitWorkfile()
	gowork = modload.WorkFilePath()

	if gowork == "" {
		base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
	}
	workFile, err := modload.ReadWorkFile(gowork)
	if err != nil {
		base.Fatalf("go: %v", err)
	}
	workDir := filepath.Dir(gowork) // 绝对，因为gowork本身是绝对的。

	haveDirs := make(map[string][]string) // 绝对值→ 原始
	for _, use := range workFile.Use {
		var abs string
		if filepath.IsAbs(use.Path) {
			abs = filepath.Clean(use.Path)
		} else {
			abs = filepath.Join(workDir, use.Path)
		}
		haveDirs[abs] = append(haveDirs[abs], use.Path)
	}

	// keepDirs将每个绝对路径映射为用于
	// 该路径的文字字符串（绝对或相对路径），或者如果
	// 则映射为空字符串。绝对路径的所有条目都应删除。
	keepDirs := make(map[string]string)

	// lookDir更新keepDirs中目录dir的条目，
	// ，它是绝对的或相对于当前工作目录
	// （不一定是包含工作文件的目录）。
	lookDir := func(dir string) {
		absDir, dir := pathRel(workDir, dir)

		fi, err := fsys.Stat(filepath.Join(absDir, "go.mod"))
		if err != nil {
			if os.IsNotExist(err) {
				keepDirs[absDir] = ""
			} else {
				base.Errorf("go: %v", err)
			}
			return
		}

		if !fi.Mode().IsRegular() {
			base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod"))
		}

		if dup := keepDirs[absDir]; dup != "" && dup != dir {
			base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
		}
		keepDirs[absDir] = dir
	}

	if len(args) == 0 {
		base.Fatalf("go: 'go work use' requires one or more directory arguments")
	}
	for _, useDir := range args {
		absArg, _ := pathRel(workDir, useDir)

		info, err := fsys.Stat(absArg)
		if err != nil {
			// 从操作系统引发的错误。Stat的格式更便于用户使用。
			if os.IsNotExist(err) {
				base.Errorf("go: directory %v does not exist", absArg)
			} else {
				base.Errorf("go: %v", err)
			}
			continue
		} else if !info.IsDir() {
			base.Errorf("go: %s is not a directory", absArg)
			continue
		}

		if !*useR {
			lookDir(useDir)
			continue
		}

		// 为仍然存在的任何子目录添加或删除条目。
		fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if !info.IsDir() {
				if info.Mode()&fs.ModeSymlink != 0 {
					if target, err := fsys.Stat(path); err == nil && target.IsDir() {
						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
					}
				}
				return nil
			}
			lookDir(path)
			return nil
		})

		// 删除不再存在的子目录的条目。
		// 因为它们不存在，所以步行时会跳过它们。
		for absDir, _ := range haveDirs {
			if str.HasFilePathPrefix(absDir, absArg) {
				if _, ok := keepDirs[absDir]; !ok {
					keepDirs[absDir] = "" // 标记删除。
				}
			}
		}
	}

	base.ExitIfErrors()

	for absDir, keepDir := range keepDirs {
		nKept := 0
		for _, dir := range haveDirs[absDir] {
			if dir == keepDir { // （注意dir始终非空）
				nKept++
			} else {
				workFile.DropUse(dir)
			}
		}
		if keepDir != "" && nKept != 1 {
			// 如果我们保存了多个副本，请将其全部删除。
			// 我们将使用AddUse重新创建一个唯一副本。
			if nKept > 1 {
				workFile.DropUse(keepDir)
			}
			workFile.AddUse(keepDir, "")
		}
	}
	modload.UpdateWorkFile(workFile)
	modload.WriteWorkFile(gowork, workFile)
}

// pathRel返回在
// go中使用的dir的绝对形式和规范形式。工作文件位于workDir目录中。
// 
// 如果dir是相对的，那么它是相对于base的不受保护的。Cwd（）
// 如果可能的话，其标准形式是相对于workDir的。
// 如果dir是绝对的，或者不能相对于workDir，则
// 其标准形式是绝对的。
// 
// 规范绝对路径是干净的。
// 规范相对路径是干净的，用斜线分隔。
func pathRel(workDir, dir string) (abs, canonical string) {
	if filepath.IsAbs(dir) {
		abs = filepath.Clean(dir)
		return abs, abs
	}

	abs = filepath.Join(base.Cwd(), dir)
	rel, err := filepath.Rel(workDir, abs)
	if err != nil {
		// 路径不能相对于go设置。工作文件，
		// 所以它必须保持绝对值。
		return abs, abs
	}

	// 规范化相对路径以使用斜杠，以便签入。work 
	// repo中具有相对路径的文件与平台无关。
	return abs, modload.ToDirectoryPath(rel)
}
